//
// Created by meow star on 2019/10/3.
//
#ifndef RCMD_RECALL_INVERT_INDEX_CPP
#define RCMD_RECALL_INVERT_INDEX_CPP


#include <fstream>
#include <json.hpp>
#include "recall_types.h"
using namespace std;
using namespace nlohmann;

class RecallElemBase{
public:
    RecallElemBase(ForwardIndex* fi, json& data_array){
        int tmpid = data_array[0].get<int>();
        data_array.erase(data_array.begin());
        _vec_data = data_array.get<vector<float>>();
        _id = tmpid;
        _fi = fi;
        /*
        if (fi->get_elem(_id) == NULL){
            spdlog::error("id not exist, id={}, tmpid={}, fi_count={}",_id, tmpid, fi->_mp_itemid_2_item.size());
        }
        */
    }
    const string& key(){
        return _fi->lineno_2_key(_id);
    }
    uint32_t id(){
        return _id;
    }
    virtual float value() = 0;
    bool operator > (RecallElemBase& b){
        return value() > b.value();
    }
    bool operator >= (RecallElemBase& b){
        return value() >= b.value();
    }
    bool operator < (RecallElemBase& b){
        return value() < b.value();
    }
    bool operator <= (RecallElemBase& b){
        return value() <= b.value();
    }
    bool operator == (RecallElemBase& b){
        return value() == b.value();
    }
public:
    vector<float> _vec_data;
    uint32_t _id;
    ForwardIndex* _fi;
};

class EzElem:public RecallElemBase{
public:
    EzElem(ForwardIndex* fi, json& data_array):RecallElemBase(fi, data_array){}
    float value(){
        return _vec_data[0];
    }
};

template <typename T>
class InvertIndex{
public:
    InvertIndex(ForwardIndex* fi, string iindex_set_key, string iindex_key)
    {
        _fi = fi;
        _iindex_set_key = iindex_set_key;
        _iindex_key = iindex_key;
    }
    int put_elem(string key, json& elem);
    int recall(int count, string dkey,vector<string> limit_iindex_keys, unordered_set<string> filter, vector<uint32_t>& result, vector<float>& weights);
    int size(){
        size_t count = 0;
        for(auto it = _mp_invert_key.begin(); it != _mp_invert_key.end(); ++ it){
            count += it->second.size();
        }
        return (int) count;
    }
    int keys(vector<string>& keys){
        for(auto it = _mp_invert_key.begin(); it != _mp_invert_key.end(); ++ it){
            keys.push_back(it->first);
        }
        return _mp_invert_key.size();
    }
public:
    unordered_map<string, list<T>> _mp_invert_key;
    ForwardIndex* _fi;
    string _iindex_set_key;
    string _iindex_key;
};

template<typename T>
class InvertIndexSet{
public:
    InvertIndexSet(ForwardIndex* fi, string iindex_set_key, string obj_path){
        _obj_path = obj_path;
        _iindex_set_key = iindex_set_key;
        _last_reload = 0;
        _obj_timestamp = 0;
        _buffer_busy_flag = 0;
        _fi = fi;
    }
    int update_reload_flag(uint32_t ts){
        _obj_timestamp = ts;
        switch_buffer();
        return 0;
    }
    unordered_map<string, InvertIndex<T>*>& get_idle_buffer(){
        int idle = _buffer_busy_flag == 0 ? 1: 0;
        return _mp_iindex_sets[idle];
    }

    unordered_map<string, InvertIndex<T>*>& get_busy_buffer(){
        return _mp_iindex_sets[_buffer_busy_flag];
    }

    void switch_buffer(){
        spdlog::error("InvertIndexSet.switch_buffer, _iindex_set_key={}, old_count={}, new_count={}", _iindex_set_key, get_busy_buffer().size(), get_idle_buffer().size());
        _buffer_busy_flag = _buffer_busy_flag == 0 ? 1 : 0;
    }

    //为了不加锁，空闲buffer的清空不能在switch的时候直接做，因为那个时候可能还有请求在访问
    void cleanup_idle_buffer(){
        auto& idle_buffer = get_idle_buffer();
        for(auto it = idle_buffer.begin(); it != idle_buffer.end(); ++ it){
            InvertIndex<T>* iindex = it->second;
            if (iindex){
                delete iindex;
            }
        }
        idle_buffer.clear();
    }
    int check_reload_object();
    int reload_object();
    int recall(int count, string dkey, vector<string> limit_iindex_keys, vector<string>& sources, const unordered_set<string>& filter, vector<const ElemInfo*>& results, vector<float>& weights);
    int stat_keys(unordered_map<string, int>& result);
    int get_status(json& result);

public:
    string _obj_path;
    string _iindex_set_key;
    long _last_reload;
    long _obj_timestamp;
    int _buffer_busy_flag;
    ForwardIndex* _fi;
    vector<pair<string, int>> _tops_keys;
    unordered_map<string, InvertIndex<T>*> _mp_iindex_sets[2];
};

template<class T>
int InvertIndex<T>::put_elem(string key, json& j) {
    T elem(_fi, j);
    auto it_list = _mp_invert_key.find(key);
    if (it_list == _mp_invert_key.end()){
        _mp_invert_key[key] = list<T>();
        it_list = _mp_invert_key.find(key);
    }
    list<T>& ls = it_list->second;
    bool found = false;
    int score_error = 0;
    for(auto it = ls.begin(); it != ls.end(); ++ it){
        if(*it <= elem){
            ls.insert(it, elem);
            found = true;
            break;
        }else{
            score_error += 1;
        }
    }
    if (score_error > 0){
        spdlog::info("score error, iskey={}, ikey={}, key={}, count={}", _iindex_set_key, _iindex_key, key, score_error);
    }

    if(!found){
        ls.push_back(elem);
    }
    return 0;
}

template<class T>
int InvertIndex<T>::recall(int count, string dkey, vector<string> limit_iindex_keys, unordered_set<string> filter, vector<uint32_t>& result, vector<float>& weights){
    auto it = _mp_invert_key.find(dkey);
    if (it == _mp_invert_key.end()){
        spdlog::info("iindex, dkey not found, dkey={}", dkey);
        return -1;
    }

    int got_count = 0;
    int filtered_count = 0;
    auto& lst = it->second;
    for(auto it_lst = lst.begin(); it_lst != lst.end(); ++ it_lst){
        T& item = *it_lst;
        if (filter.find(item.key()) == filter.end()){
            got_count ++;
            if (limit_iindex_keys.empty()){
                result.push_back(item.id());
                weights.push_back(item.value());
            }else{
                const ElemInfo* elem = _fi->get_elem(item.id());
                /*
                if(elem->is_subset_of(_iindex_set_key, limit_iindex_keys)){
                    result.push_back(item.id());
                    weights.push_back(item.value());
                }
                */
                bool skip = false;
                for(const string& limit : limit_iindex_keys){
                    const char* limit_cstr = limit.c_str();
                    int direction = limit_cstr[0] == '-' ? -1 : 1;
                    limit_cstr ++;
                    bool if_match = elem->filter_pattern(limit_cstr);
                    if ((direction > 0 && !if_match) || (direction < 0 && if_match)){
                        skip = true;
                        break;
                    }
                }
                if (skip){
                    continue;
                }
                result.push_back(item.id());
                weights.push_back(item.value());
            }

            if (result.size() >= count){
                break;
            }
        }
        else{
            filtered_count ++;
        }
    }
    spdlog::info("iindex,dkey={}, filtered_count={}, got_count={}, size={}, need_count={}", dkey, filtered_count, got_count, lst.size(),count);
    return got_count;
}

template<class T>
int InvertIndexSet<T>::check_reload_object() {
    long last_modify = FileUtils::get_modify_time(_obj_path.data());
    if (last_modify - _obj_timestamp > MODEL_RELOAD_DELAY){
        return 1;
    }
    return 0;
}


template<class T>
int InvertIndexSet<T>::reload_object() {
    ifstream json_file(_obj_path);
    int ret = 0;
    if (json_file.is_open())
    {
        timeval ts;
        gettimeofday(&ts, NULL);
        uint32_t start = ts.tv_sec;
        int total = 0;
        json j;
        vector<pair<string, int>> tops;
        //这个时候才清除上一轮被switch下来的空闲缓存中的内容
        cleanup_idle_buffer();
        auto& idle_buffer = get_idle_buffer();
        try{
            json_file >> j;
            for(json::iterator it_key = j.begin(); it_key != j.end(); ++ it_key)
            {
                string key = it_key.key();
                json& source_data = it_key.value();
                InvertIndex<T>* iindex = new InvertIndex<T>(_fi, _iindex_set_key, key);
                int count = 0;
                for(json::iterator it_source = source_data.begin(); it_source != source_data.end(); ++ it_source){
                    int source_count = 0;
                    string source = it_source.key();
                    for(int i = it_source.value().size() - 1; i>=0 ; i --){
                        iindex->put_elem(it_source.key(), it_source.value()[i]);
                        total += 1;
                        count += 1;
                        source_count += 1;
                    }
                    if ( _iindex_set_key == "tops"){
                        spdlog::info("key={}, source={}, count={}",key, source, source_count);
                    }

                }
                auto tops_pair = pair<string, int>(key, count);
                tops.push_back(tops_pair);
                auto it = idle_buffer.find(key);
                if (it == idle_buffer.end()){
                    idle_buffer[key] = iindex;
                } else{
                    InvertIndex<T>* old = idle_buffer[key];
                    idle_buffer[key] = iindex;
                    delete old;
                }
            }
            //sort(tops.begin(), tops.end(), [](const pair<string, int>& a, const pair<string, int>& b){return a.second > b.second;});
            _tops_keys = tops;
        }
        catch (json::exception e){
            spdlog::info("reload iindex failed, error={}", e.what());
            ret  = -1;
        }
        gettimeofday(&ts, NULL);
        spdlog::info("reload done, ret={}, count={}, cost={}", ret, total, ts.tv_sec - start);
        return ret;
    }
    else{
        spdlog::info("reload error, open file failed, file={}", _obj_path);
        return -1;
    }
}

template<class T>
int InvertIndexSet<T>::get_status(json& result){
    auto& working_buffer = get_busy_buffer();
    for (auto it = working_buffer.begin(); it != working_buffer.end(); ++ it){
        json data;
        json sources;
        InvertIndex<T>* iindex = it->second;
        vector<string> source_keys;
        iindex->keys(source_keys);
        for (auto it_source_key = source_keys.begin(); it_source_key != source_keys.end(); ++ it_source_key){
            sources.push_back(*it_source_key);
        }
        data["count"] = iindex->size();
        data["sources"] = sources;
        result[it->first]  = data;
    }
    return 0;
}

template<class T>
int InvertIndexSet<T>::stat_keys(unordered_map<string, int> &result) {
    auto& working_buffer = get_busy_buffer();
    for (auto it = working_buffer.begin(); it != working_buffer.end(); ++ it){
        if (result.find(it->first) == result.end()){
            result[it->first] = 0;
        }
        result[it->first] += it->second->size();
    }
    return 0;
}

template<class T>
int InvertIndexSet<T>::recall(int count, string dkey, vector<string> limit_iindex_keys, vector<string>& sources, const unordered_set<string>& filter, vector<const ElemInfo*>& results, vector<float>& weights){
    vector<uint32_t> rst;
    vector<float> recall_weights;
    int got = 0;
    auto& working_buffer = get_busy_buffer();
    auto it_key = working_buffer.find(dkey);
    if (it_key != working_buffer.end()){

        InvertIndex<T>* iindex = it_key->second;
        if (sources.empty()){
            list<string> keys;
            iindex->keys(sources);
        }
        spdlog::info("source count={}",sources.size());
        for(size_t i = 0 ;i < sources.size(); ++ i){
            int ret = iindex->recall(count*(i+1), sources[i], limit_iindex_keys, filter, rst, recall_weights);
            if (ret > 0){
                got += ret;
            }
        }
    }
    else if(dkey == "_tops"){
        vector<string> tops_category;

        for(int i = 0; i < _tops_keys.size() && i < count; ++ i){
            string key = _tops_keys[i].first;
            auto it_index = working_buffer.find(key);
            if (it_index == working_buffer.end())
            {
                continue;
            }
            InvertIndex<T>* iindex = it_index->second;
            if (sources.empty()){
                list<string> keys;
                iindex->keys(sources);
            }

            for(size_t i = 0;i < sources.size(); ++ i){
                int ret = iindex->recall(5, sources[i],limit_iindex_keys, filter, rst, recall_weights);
                if (ret > 0){
                    got += ret;
                }
            }
        }
    }
    else{
        spdlog::error("recall key not exists, key={}", dkey);
    }


    for(size_t i = 0; i < rst.size(); i ++){
        const ElemInfo* elem = _fi->get_elem(rst[i]);
        float& weight = recall_weights[i];
        if (elem){
            results.push_back(elem);
            weights.push_back(weight);
        }
    }
    return got;
}
#endif